package br.com.hs.nfe.common.util;

import br.com.hs.nfe.common.anotation.AnotationConstants;
import br.com.hs.nfe.common.anotation.Element;
import br.com.hs.nfe.common.anotation.NotInsert;
import br.com.hs.nfe.common.enumeration.SituacaoNFeEnum;
import br.com.hs.nfe.common.exception.HSConverterException;
import br.com.hs.nfe.common.to.*;
import br.inf.portalfiscal.nfe.NFeDocument;
import br.inf.portalfiscal.nfe.TLocal;
import br.inf.portalfiscal.nfe.TNFe;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Dest;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Det.Imposto;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Det.Imposto.COFINS;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Det.Imposto.COFINSST;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Det.Imposto.ICMS;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Det.Imposto.II;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Det.Imposto.IPI;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Det.Imposto.ISSQN;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Det.Imposto.PIS;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Det.Imposto.PISST;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Det.Prod;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Emit;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.InfAdic;
import br.inf.portalfiscal.nfe.TNFe.InfNFe.Transp;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.xmlbeans.StringEnumAbstractBase;
import org.apache.xmlbeans.XmlObject;

/**
 * Classe responsável pela conversão dos TAD Objects para XML e vice-versa.
 * @author Ranlive Hrysyk
 */
public class DocumentConverter {
    
    private static final String CLASS_FACTORY = "Factory";
    private static final String METHOD_NEW_INSTANCE = "newInstance";
    private static final String METHOD_SET = "set";
    private static final String METHOD_GET = "get";
    private static final String METHOD_FOR_STRING = "forString";

    /*
     * Lança uma exceção do tipo HSConverterException. 
     */
    private static void lauchException(Exception exception) throws HSConverterException {
        throw new HSConverterException(exception.getMessage(), exception);
    }

    /*
     * Método responsável por copiar dados de um objeto de mesma classe.
     */
    private static void copy(Object source, Object destiny) throws HSConverterException {
        try {
            if ((source != null) && (destiny != null) && (source.getClass().equals(destiny.getClass()))) {
                for (Field field : source.getClass().getDeclaredFields()) {
                    field.setAccessible(true);
                    Object value = field.get(source);
                    if (value != null) {
                        Field destinyField = destiny.getClass().getDeclaredField(field.getName());
                        destinyField.setAccessible(true);
                        destinyField.set(destiny, value);
                    }
                }
            }
        } catch (Exception ex) {
            lauchException(ex);
        }
    }

    /*
     * Método responsável por instanciar XmlObject pela classe.
     */
    private static XmlObject newInstance(Class clazz) throws HSConverterException {
        XmlObject xmlObject = null;
        try {
            //Faz uma busca pelas classes
            for (Class classFactory : clazz.getDeclaredClasses()) {
                //Verifica se é a classe Factory
                if (classFactory.getSimpleName().equals(CLASS_FACTORY)) {
                    //Faz uma busca pelo método newInstance
                    Method newInstance = classFactory.getDeclaredMethod(METHOD_NEW_INSTANCE, (Class[]) null);
                    //Verifica se método não é null
                    if (newInstance != null) {
                        //Invoca o método
                        xmlObject = (XmlObject) newInstance.invoke(xmlObject, (Object[]) null);
                        break;
                    }
                }
            }
        } catch (Exception ex) {
            lauchException(ex);
        }
        return xmlObject;
    }

    /*
     * Monta o nome do método
     */
    private static String mountMethodName(String field, String init) {
        return ((field != null) && (!field.isEmpty())) ? new StringBuilder().append(init).append(field.substring(0, 1).toUpperCase()).append(field.substring(1)).toString() : null;
    }

    /*
     * Invoca um método set.
     */
    private static void invoke(Object source, String field, Class clazz, Object value) throws HSConverterException {
        try {
            Method method = source.getClass().getDeclaredMethod(mountMethodName(field, METHOD_SET), clazz);
            if (method != null) {
                method.invoke(source, value);
            }
        } catch (NoSuchMethodException e) {
            return;
        } catch (Exception ex) {
            lauchException(ex);
        }
    }

    /*
     * Invoca um método get.
     */
    private static Object invoke(Object source, String field) throws HSConverterException {
        Object object = null;
        try {
            Method method = source.getClass().getDeclaredMethod(mountMethodName(field, METHOD_GET), (Class[]) null);
            if (method != null) {
                object = method.invoke(source, (Object[]) null);
            }
        } catch (NoSuchMethodException e) {
            return null;
        } catch (Exception ex) {
            lauchException(ex);
        }
        return object;
    }

    /*
     * Efetua uma busca pelo enum do XML e retorna o tipo especificado.
     */
    private static StringEnumAbstractBase forString(Class clazz, String value) throws HSConverterException {
        try {
            for (Method method : clazz.getDeclaredMethods()) {
                if (method.getName().equals(METHOD_FOR_STRING)) {
                    return (StringEnumAbstractBase) method.invoke(clazz, value);
                }
            }
        } catch (Exception ex) {
            lauchException(ex);
        }
        
        return null;
    }

    /*
     * Converte BaseTO em XmlObject.
     */
    private static XmlObject createXmlObject(BaseTO source, Class destiny) throws HSConverterException {
        boolean hasData = false;
        XmlObject xml = null;

        //Verifica se BaseTO não é nulo
        if (source != null) {
            xml = newInstance(destiny);

            //Verifica se conseguiu obter uma instancia de XmlObject
            if (xml != null) {
                for (Field field : source.getClass().getDeclaredFields()) {
                    //Torna o campo acessível
                    field.setAccessible(true);

                    //Verifica se possui a anotação @Element e se não possui a anotação @NotInsert
                    if ((!field.isAnnotationPresent(NotInsert.class)) && (field.isAnnotationPresent(Element.class))) {
                        Element element = field.getAnnotation(Element.class);
                        String fieldName = !element.elementName().equals(AnotationConstants.CONSTANT_DEFAULT_NAME) ? element.elementName() : field.getName();

                        //Obtém o valor
                        Object value = invoke(source, fieldName);
                        //Verifica se value não é nulo
                        if (value != null) {
                            //Há dados
                            hasData = true;
                            
                            switch (element.type().ordinal()) {
                                case 0: //C - Caracter
                                case 1: //N - Numérico
                                    invoke(xml, fieldName, String.class, value.toString().trim());
                                    break;
                                case 2: //D - Data                                    
                                    Date date = (Date) value;
                                    SimpleDateFormat sd = new SimpleDateFormat(element.formart());
                                    invoke(xml, fieldName, String.class, sd.format(date));
                                    break;
                                case 3: //E - Enum
                                    for (Class clazz : element.classType()) {
                                        if (clazz.equals(String.class)) {
                                            invoke(xml, fieldName, String.class, value.toString());
                                            break;
                                        } else {
                                            StringEnumAbstractBase xmlEnum = forString(clazz, (String) value);
                                            if (xmlEnum != null) {
                                                try {
                                                    invoke(xml, fieldName, clazz, xmlEnum);
                                                } catch (HSConverterException e) {
                                                    continue;
                                                }
                                            }
                                        }
                                    }
                                    break;
                                case 4: //DC - Decimal
                                    BigDecimal decimal = ((BigDecimal) value).setScale(element.decimalCount());
                                    invoke(xml, fieldName, String.class, decimal.toString());
                                    break;
                                case 5: //ND - Nó
                                    for (Class clazz : element.classType()) {
                                        XmlObject xmlValue = createXmlObject((BaseTO) value, clazz);
                                        if (xmlValue != null) {
                                            invoke(xml, fieldName, clazz, xmlValue);
                                        }
                                    }
                                    break;
                                case 6: //AR - Array
                                    if (value.getClass().isArray()) {
                                        //Obtém o array
                                        Object[] values = (Object[]) value;
                                        //Faz um loop pelas classes
                                        for (Class clazz : element.classType()) {
                                            //Cria um instância genérica de um array
                                            Object xmlValues = Array.newInstance(clazz, values.length);
                                            for (int i = 0; i < values.length; i++) {
                                                //Cria o xml de cada item do array
                                                Object xmlValue = createXmlObject((BaseTO) values[i], clazz);
                                                if (xmlValue != null) {
                                                    //Adiciona o item ao array
                                                    Array.set(xmlValues, i, xmlValue);
                                                }
                                            }
                                            invoke(xml, fieldName, Array.newInstance(clazz, 0).getClass(), xmlValues);
                                        }
                                    }
                                    break;
                            }
                        }
                    }
                }
            }
        }
        //Se há dados retorna um XmlObeject ou null se não há
        return hasData ? xml : null;
    }

    /*
     * Converte XmlObject em BaseTO.
     */
    private static BaseTO createBaseTO(XmlObject xml, Class destiny) throws HSConverterException {
        boolean hasData = false;
        BaseTO baseTO = null;
        
        try {
            //Verifica se xml e destiny não é nulo
            if ((xml != null) && (destiny != null)) {
                baseTO = (BaseTO) destiny.newInstance();

                //Faz um loop pelos campos de baseTO
                for (Field field : destiny.getDeclaredFields()) {
                    //Torna o campo acessível
                    field.setAccessible(true);

                    //Verifica se possui a anotação @Element 
                    if (field.isAnnotationPresent(Element.class)) {
                        Element element = field.getAnnotation(Element.class);
                        String fieldName = !element.elementName().equals(AnotationConstants.CONSTANT_DEFAULT_NAME) ? element.elementName() : field.getName();

                        //Obtém o valor
                        Object value = invoke(xml, fieldName);
                        //Verifica se value não é nulo
                        if (value != null) {
                            //Há dados
                            hasData = true;
                            
                            switch (element.type().ordinal()) {
                                case 0: //C - Caracter
                                case 1: //N - Numérico  
                                case 3: //E - Enum
                                    invoke(baseTO, fieldName, String.class, value.toString());
                                    break;
                                case 2: //D - Data
                                    SimpleDateFormat sd = new SimpleDateFormat(element.formart());
                                    Date date = sd.parse((String) value);
                                    invoke(baseTO, fieldName, Date.class, date);
                                    break;
                                case 4: //DC - Decimal
                                    invoke(baseTO, fieldName, BigDecimal.class, new BigDecimal((String) value));
                                    break;
                                case 5: //ND - Nó
                                    for (Class clazz : element.classType()) {
                                        if (clazz.isInstance(value)) {
                                            BaseTO base = createBaseTO((XmlObject) value, field.getType());
                                            invoke(baseTO, fieldName, field.getType(), base);
                                        }
                                    }
                                    break;
                                case 6: //AR - Array
                                    if (value.getClass().isArray()) {
                                        //Obtém o array
                                        Object[] values = (Object[]) value;
                                        //Faz um loop pelas classes
                                        for (Class clazz : element.classType()) {
                                            //Verifica se o valor é uma das instancicas das classes declaradas
                                            if (Array.newInstance(clazz, 0).getClass().isInstance(values)) {
                                                //Obtém a classe do tipo genérico parametrizado
                                                ParameterizedType fieldType = (ParameterizedType) field.getGenericType();
                                                Class<?> fieldListClass = (Class<?>) fieldType.getActualTypeArguments()[0];
                                                //Cria um instância genérica de um array
                                                Object baseTOArray = Array.newInstance(fieldListClass, values.length);
                                                for (int i = 0; i < values.length; i++) {
                                                    //Cria o BaseTO de cada item do array 
                                                    Object base = createBaseTO((XmlObject) values[i], fieldListClass);
                                                    if (base != null) {
                                                        //Adiciona o item ao array
                                                        Array.set(baseTOArray, i, base);
                                                    }
                                                }
                                                invoke(baseTO, fieldName, Array.newInstance(fieldListClass, 0).getClass(), baseTOArray);
                                            }
                                        }
                                    }
                                    break;
                            }
                        }
                    }
                }
            }
        } catch (Exception ex) {
            lauchException(ex);
        }
        //Se há dados retorna um BaseTO ou null se não há
        return hasData ? baseTO : null;
    }

    /**
     * Converte EmitenteTO em TNFe.InfNFe.Emit.
     */
    public static TNFe.InfNFe.Emit createEmit(EmitenteTO emitenteTO) throws HSConverterException {
        return (Emit) createXmlObject(emitenteTO, TNFe.InfNFe.Emit.class);
    }

    /**
     * Converte TNFe.InfNFe.Emit em EmitenteTO.
     */
    public static EmitenteTO createEmitenteTO(TNFe.InfNFe.Emit emit) throws HSConverterException {
        return (EmitenteTO) createBaseTO(emit, EmitenteTO.class);
    }

    /**
     * Converte ClienteTO em TNFe.InfNFe.Dest.
     */
    public static TNFe.InfNFe.Dest createDest(ClienteTO clienteTO) throws HSConverterException {
        return (Dest) createXmlObject(clienteTO, TNFe.InfNFe.Dest.class);
    }

    /**
     * Converte TNFe.InfNFe.Dest em ClienteTO.
     */
    public static ClienteTO createClienteTO(TNFe.InfNFe.Dest dest) throws HSConverterException {
        return (ClienteTO) createBaseTO(dest, ClienteTO.class);
    }

    /**
     * Converte ProdutoTO em TNFe.InfNFe.Det.Prod.
     */
    public static TNFe.InfNFe.Det.Prod createProd(ProdutoTO produtoTO) throws HSConverterException {
        return (Prod) createXmlObject(produtoTO, TNFe.InfNFe.Det.Prod.class);
    }

    /**
     * Converte TNFe.InfNFe.Det.Prod em ProdutoTO.
     */
    public static ProdutoTO createProdutoTO(TNFe.InfNFe.Det.Prod prod) throws HSConverterException {
        return (ProdutoTO) createBaseTO(prod, ProdutoTO.class);
    }

    /**
     * Converte IcmsTO em TNFe.InfNFe.Det.Imposto.ICMS.
     */
    public static TNFe.InfNFe.Det.Imposto.ICMS createICMS(IcmsTO icmsTO) throws HSConverterException {
        if (icmsTO != null) {
            TNFe.InfNFe.Det.Imposto.ICMS icms = null;
            if (icmsTO.getCSTEnum() != null) {
                icms = ICMS.Factory.newInstance();
                switch (icmsTO.getCSTEnum().ordinal()) {
                    case 0: //ICMS00
                        ICMS.ICMS00 icms00 = (ICMS.ICMS00) createXmlObject(icmsTO, ICMS.ICMS00.class);
                        icms.setICMS00(icms00);
                        break;
                    case 1: //ICMS10
                        ICMS.ICMS10 icms10 = (ICMS.ICMS10) createXmlObject(icmsTO, ICMS.ICMS10.class);
                        icms.setICMS10(icms10);
                        break;
                    case 2: //ICMS20
                        ICMS.ICMS20 icms20 = (ICMS.ICMS20) createXmlObject(icmsTO, ICMS.ICMS20.class);
                        icms.setICMS20(icms20);
                        break;
                    case 3: //ICMS30
                        ICMS.ICMS30 icms30 = (ICMS.ICMS30) createXmlObject(icmsTO, ICMS.ICMS30.class);
                        icms.setICMS30(icms30);
                        break;
                    case 4: //ICMS40
                    case 5: //ICMS41
                    case 6: //ICMS50
                        ICMS.ICMS40 icms40 = (ICMS.ICMS40) createXmlObject(icmsTO, ICMS.ICMS40.class);
                        icms.setICMS40(icms40);
                        break;
                    case 7: //ICMS51
                        ICMS.ICMS51 icms51 = (ICMS.ICMS51) createXmlObject(icmsTO, ICMS.ICMS51.class);
                        icms.setICMS51(icms51);
                        break;
                    case 8: //ICMS60
                        ICMS.ICMS60 icms60 = (ICMS.ICMS60) createXmlObject(icmsTO, ICMS.ICMS60.class);
                        icms.setICMS60(icms60);
                        break;
                    case 9: //ICMS70
                        ICMS.ICMS70 icms70 = (ICMS.ICMS70) createXmlObject(icmsTO, ICMS.ICMS70.class);
                        icms.setICMS70(icms70);
                        break;
                    case 10: //ICMS90
                        ICMS.ICMS90 icms90 = (ICMS.ICMS90) createXmlObject(icmsTO, ICMS.ICMS90.class);
                        icms.setICMS90(icms90);
                        break;
                    case 11: //ICMSPart 10
                    case 12: //ICMSPart 90
                        ICMS.ICMSPart icmsPart = (ICMS.ICMSPart) createXmlObject(icmsTO, ICMS.ICMSPart.class);
                        icms.setICMSPart(icmsPart);
                        break;
                    case 13: //ICMSST
                        ICMS.ICMSST icmsST = (ICMS.ICMSST) createXmlObject(icmsTO, ICMS.ICMSST.class);
                        icms.setICMSST(icmsST);
                        break;
                }
            } else if (icmsTO.getCSOSNEnum() != null) {
                icms = ICMS.Factory.newInstance();
                switch (icmsTO.getCSOSNEnum().ordinal()) {
                    case 0: //CSOSN = 101
                        ICMS.ICMSSN101 icmssn101 = (ICMS.ICMSSN101) createXmlObject(icmsTO, ICMS.ICMSSN101.class);
                        icms.setICMSSN101(icmssn101);
                        break;
                    case 1: //CSOSN = 102
                    case 2: //CSOSN = 103
                    case 6: //CSOSN = 300
                    case 7: //CSOSN = 400
                        ICMS.ICMSSN102 icmssn102 = (ICMS.ICMSSN102) createXmlObject(icmsTO, ICMS.ICMSSN102.class);
                        icms.setICMSSN102(icmssn102);
                        break;
                    case 3: //CSOSN = 201
                        ICMS.ICMSSN201 icmssn201 = (ICMS.ICMSSN201) createXmlObject(icmsTO, ICMS.ICMSSN201.class);
                        icms.setICMSSN201(icmssn201);
                        break;
                    case 4: //CSOSN = 202
                    case 5: //CSOSN = 203
                        ICMS.ICMSSN202 icmssn202 = (ICMS.ICMSSN202) createXmlObject(icmsTO, ICMS.ICMSSN202.class);
                        icms.setICMSSN202(icmssn202);
                        break;
                    case 8: //CSOSN = 500
                        ICMS.ICMSSN500 icmssn500 = (ICMS.ICMSSN500) createXmlObject(icmsTO, ICMS.ICMSSN500.class);
                        icms.setICMSSN500(icmssn500);
                        break;
                    case 9: //CSOSN = 900
                        ICMS.ICMSSN900 icmssn900 = (ICMS.ICMSSN900) createXmlObject(icmsTO, ICMS.ICMSSN900.class);
                        icms.setICMSSN900(icmssn900);
                        break;
                }
            }
            return icms;
        }
        return null;
    }

    /**
     * Converte TNFe.InfNFe.Det.Imposto.ICMS em IcmsTO.
     */
    public static IcmsTO createIcmsTO(TNFe.InfNFe.Det.Imposto.ICMS icms) throws HSConverterException {
        IcmsTO icmsTO = null;
        if (icms != null) {
            if (icms.isSetICMS00()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMS00(), IcmsTO.class);
            } else if (icms.isSetICMS10()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMS10(), IcmsTO.class);
            } else if (icms.isSetICMS20()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMS20(), IcmsTO.class);
            } else if (icms.isSetICMS30()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMS30(), IcmsTO.class);
            } else if (icms.isSetICMS40()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMS40(), IcmsTO.class);
            } else if (icms.isSetICMS51()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMS51(), IcmsTO.class);
            } else if (icms.isSetICMS60()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMS60(), IcmsTO.class);
            } else if (icms.isSetICMS70()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMS70(), IcmsTO.class);
            } else if (icms.isSetICMS90()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMS90(), IcmsTO.class);
            } else if (icms.isSetICMSPart()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMSPart(), IcmsTO.class);
            } else if (icms.isSetICMSST()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMSST(), IcmsTO.class);
            } else if (icms.isSetICMSSN101()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMSSN101(), IcmsTO.class);
            } else if (icms.isSetICMSSN102()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMSSN102(), IcmsTO.class);
            } else if (icms.isSetICMSSN201()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMSSN201(), IcmsTO.class);
            } else if (icms.isSetICMSSN202()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMSSN202(), IcmsTO.class);
            } else if (icms.isSetICMSSN500()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMSSN500(), IcmsTO.class);
            } else if (icms.isSetICMSSN900()) {
                icmsTO = (IcmsTO) createBaseTO(icms.getICMSSN900(), IcmsTO.class);
            }
        }
        return icmsTO;
    }

    /**
     * Converte IpiTO em TNFe.InfNFe.Det.Imposto.IPI.
     */
    public static TNFe.InfNFe.Det.Imposto.IPI createIPI(IpiTO ipiTO) throws HSConverterException {
        if (ipiTO != null) {
            TNFe.InfNFe.Det.Imposto.IPI ipi = (TNFe.InfNFe.Det.Imposto.IPI) createXmlObject(ipiTO, TNFe.InfNFe.Det.Imposto.IPI.class);
            
            if (ipiTO.getCSTEnum() != null) {
                switch (ipiTO.getCSTEnum().ordinal()) {
                    case 0: //CST = 00
                    case 6: //CST = 49
                    case 7: //CST = 50
                    case 13: //CST = 99
                        IPI.IPITrib ipiTrib = (IPI.IPITrib) createXmlObject(ipiTO, IPI.IPITrib.class);
                        ipi.setIPITrib(ipiTrib);
                        break;
                    case 1: //CST = 01
                    case 2: //CST = 02
                    case 3: //CST = 03
                    case 4: //CST = 04
                    case 5: //CST = 05
                    case 8: //CST = 51
                    case 9: //CST = 52
                    case 10: //CST = 53
                    case 11: //CST = 54
                    case 12: //CST = 55
                        IPI.IPINT ipiNT = (IPI.IPINT) createXmlObject(ipiTO, IPI.IPINT.class);
                        ipi.setIPINT(ipiNT);
                        break;
                }
            }
            return ipi;
        }
        return null;
    }

    /**
     * Converte TNFe.InfNFe.Det.Imposto.IPI em IpiTO;
     */
    public static IpiTO createIpiTO(TNFe.InfNFe.Det.Imposto.IPI ipi) throws HSConverterException {
        if (ipi != null) {
            IpiTO ipiTO = (IpiTO) createBaseTO(ipi, IpiTO.class);
            
            IpiTO ipiChildren = null;
            if (ipi.isSetIPITrib()) {
                ipiChildren = (IpiTO) createBaseTO(ipi.getIPITrib(), IpiTO.class);
            } else if (ipi.isSetIPINT()) {
                ipiChildren = (IpiTO) createBaseTO(ipi.getIPINT(), IpiTO.class);
            }
            copy(ipiChildren, ipiTO);
            
            return ipiTO;
        }
        return null;
    }

    /**
     * Converte IITO em TNFe.InfNFe.Det.Imposto.II.
     */
    public static TNFe.InfNFe.Det.Imposto.II createII(IITO iiTO) throws HSConverterException {
        return (II) createXmlObject(iiTO, II.class);
    }

    /**
     * Converte TNFe.InfNFe.Det.Imposto.II em IITO.
     */
    public static IITO createIITO(TNFe.InfNFe.Det.Imposto.II ii) throws HSConverterException {
        return (IITO) createBaseTO(ii, IITO.class);
    }

    /**
     * Converte PisTO em TNFe.InfNFe.Det.Imposto.PIS.
     */
    public static TNFe.InfNFe.Det.Imposto.PIS createPIS(PisTO pisTO) throws HSConverterException {
        if ((pisTO != null) && (pisTO.getCSTEnum() != null)) {
            PIS pis = PIS.Factory.newInstance();
            
            switch (pisTO.getCSTEnum().ordinal()) {
                case 0: //CST = 01
                case 1: //CST = 02
                    PIS.PISAliq pisAliq = (PIS.PISAliq) createXmlObject(pisTO, PIS.PISAliq.class);
                    pis.setPISAliq(pisAliq);
                    break;
                case 2: //CST = 03
                    PIS.PISQtde pisQtde = (PIS.PISQtde) createXmlObject(pisTO, PIS.PISQtde.class);
                    pis.setPISQtde(pisQtde);
                    break;
                case 3: //CST = 04
                case 4: //CST = 06
                case 5: //CST = 07
                case 6: //CST = 08
                case 7: //CST = 09
                    PIS.PISNT pisNT = (PIS.PISNT) createXmlObject(pisTO, PIS.PISNT.class);
                    pis.setPISNT(pisNT);
                    break;
                case 8: //CST = 49
                case 9: //CST = 50
                case 10: //CST = 51
                case 11: //CST = 52
                case 12: //CST = 53
                case 13: //CST = 54
                case 14: //CST = 55
                case 15: //CST = 56
                case 16: //CST = 60
                case 17: //CST = 61
                case 18: //CST = 62
                case 19: //CST = 63
                case 20: //CST = 64
                case 21: //CST = 65
                case 22: //CST = 66
                case 23: //CST = 67
                case 24: //CST = 70
                case 25: //CST = 71
                case 26: //CST = 72
                case 27: //CST = 73
                case 28: //CST = 74
                case 29: //CST = 75
                case 30: //CST = 98
                case 31: //CST = 99
                    PIS.PISOutr pisOutr = (PIS.PISOutr) createXmlObject(pisTO, PIS.PISOutr.class);
                    pis.setPISOutr(pisOutr);
                    break;
            }
            return pis;
        }
        return null;
    }

    /**
     * Converte TNFe.InfNFe.Det.Imposto.PIS em PisTO.
     */
    public static PisTO createPisTO(TNFe.InfNFe.Det.Imposto.PIS pis) throws HSConverterException {
        PisTO pisTO = null;
        if (pis != null) {
            if (pis.isSetPISAliq()) {
                pisTO = (PisTO) createBaseTO(pis.getPISAliq(), PisTO.class);
            } else if (pis.isSetPISQtde()) {
                pisTO = (PisTO) createBaseTO(pis.getPISQtde(), PisTO.class);
            } else if (pis.isSetPISNT()) {
                pisTO = (PisTO) createBaseTO(pis.getPISNT(), PisTO.class);
            } else if (pis.isSetPISOutr()) {
                pisTO = (PisTO) createBaseTO(pis.getPISOutr(), PisTO.class);
            }
        }
        return pisTO;
    }

    /**
     * Converte PisSTTO em TNFe.InfNFe.Det.Imposto.PISST.
     */
    public static TNFe.InfNFe.Det.Imposto.PISST createPISST(PisSTTO pisSTTO) throws HSConverterException {
        return (PISST) createXmlObject(pisSTTO, PISST.class);
    }

    /**
     * Converte TNFe.InfNFe.Det.Imposto.PISST em PisSTTO.
     */
    public static PisSTTO createPisSTTO(TNFe.InfNFe.Det.Imposto.PISST pisST) throws HSConverterException {
        return (PisSTTO) createBaseTO(pisST, PisSTTO.class);
    }

    /**
     * Converte CofinsTO em TNFe.InfNFe.Det.Imposto.COFINS.
     */
    public static TNFe.InfNFe.Det.Imposto.COFINS createCOFINS(CofinsTO cofinsTO) throws HSConverterException {
        if ((cofinsTO != null) && (cofinsTO.getCSTEnum() != null)) {
            COFINS cofins = COFINS.Factory.newInstance();
            
            switch (cofinsTO.getCSTEnum().ordinal()) {
                case 0: //CST = 01
                case 1: //CST = 02
                    COFINS.COFINSAliq cofinsAliq = (COFINS.COFINSAliq) createXmlObject(cofinsTO, COFINS.COFINSAliq.class);
                    cofins.setCOFINSAliq(cofinsAliq);
                    break;
                case 2: //CST = 03
                    COFINS.COFINSQtde cofinsQtde = (COFINS.COFINSQtde) createXmlObject(cofinsTO, COFINS.COFINSQtde.class);
                    cofins.setCOFINSQtde(cofinsQtde);
                    break;
                case 3: //CST = 04
                case 4: //CST = 06
                case 5: //CST = 07
                case 6: //CST = 08
                case 7: //CST = 09
                    COFINS.COFINSNT cofinsNT = (COFINS.COFINSNT) createXmlObject(cofinsTO, COFINS.COFINSNT.class);
                    cofins.setCOFINSNT(cofinsNT);
                    break;
                case 8: //CST = 49
                case 9: //CST = 50
                case 10: //CST = 51
                case 11: //CST = 52
                case 12: //CST = 53
                case 13: //CST = 54
                case 14: //CST = 55
                case 15: //CST = 56
                case 16: //CST = 60
                case 17: //CST = 61
                case 18: //CST = 62
                case 19: //CST = 63
                case 20: //CST = 64
                case 21: //CST = 65
                case 22: //CST = 66
                case 23: //CST = 67
                case 24: //CST = 70
                case 25: //CST = 71
                case 26: //CST = 72
                case 27: //CST = 73
                case 28: //CST = 74
                case 29: //CST = 75
                case 30: //CST = 98
                case 31: //CST = 99
                    COFINS.COFINSOutr cofinsOutr = (COFINS.COFINSOutr) createXmlObject(cofinsTO, COFINS.COFINSOutr.class);
                    cofins.setCOFINSOutr(cofinsOutr);
                    break;
            }
            return cofins;
        }
        return null;
    }

    /**
     * Converte TNFe.InfNFe.Det.Imposto.COFINS em CofinsTO.
     */
    public static CofinsTO createCofinsTO(TNFe.InfNFe.Det.Imposto.COFINS cofins) throws HSConverterException {
        CofinsTO cofinsTO = null;
        if (cofins != null) {
            if (cofins.isSetCOFINSAliq()) {
                cofinsTO = (CofinsTO) createBaseTO(cofins.getCOFINSAliq(), CofinsTO.class);
            } else if (cofins.isSetCOFINSQtde()) {
                cofinsTO = (CofinsTO) createBaseTO(cofins.getCOFINSQtde(), CofinsTO.class);
            } else if (cofins.isSetCOFINSNT()) {
                cofinsTO = (CofinsTO) createBaseTO(cofins.getCOFINSNT(), CofinsTO.class);
            } else if (cofins.isSetCOFINSOutr()) {
                cofinsTO = (CofinsTO) createBaseTO(cofins.getCOFINSOutr(), CofinsTO.class);
            }
        }
        return cofinsTO;
    }

    /**
     * Converte CofinsSTTO em TNFe.InfNFe.Det.Imposto.COFINSST.
     */
    public static TNFe.InfNFe.Det.Imposto.COFINSST createCOFINSST(CofinsSTTO cofinsSTTO) throws HSConverterException {
        return (COFINSST) createXmlObject(cofinsSTTO, COFINSST.class);
    }

    /**
     * Converte TNFe.InfNFe.Det.Imposto.COFINSST em CofinsSTTO.
     */
    public static CofinsSTTO createCofinsSTTO(TNFe.InfNFe.Det.Imposto.COFINSST cofinsST) throws HSConverterException {
        return (CofinsSTTO) createBaseTO(cofinsST, CofinsSTTO.class);
    }

    /**
     * Converte IssqnTO em TNFe.InfNFe.Det.Imposto.ISSQN.
     */
    public static TNFe.InfNFe.Det.Imposto.ISSQN createISSQN(IssqnTO issqnTO) throws HSConverterException {
        return (ISSQN) createXmlObject(issqnTO, ISSQN.class);
    }

    /**
     * Converte TNFe.InfNFe.Det.Imposto.ISSQN em IssqnTO.
     */
    public static IssqnTO createIssqnTO(TNFe.InfNFe.Det.Imposto.ISSQN issqn) throws HSConverterException {
        return (IssqnTO) createBaseTO(issqn, IssqnTO.class);
    }

    /**
     * Converte NotaFiscalTO em TNFe.InfNFe.Ide.
     */
    public static TNFe.InfNFe.Ide createIde(NotaFiscalTO notaFiscalTO) throws HSConverterException {
        return (TNFe.InfNFe.Ide) createXmlObject(notaFiscalTO, TNFe.InfNFe.Ide.class);
    }

    /**
     * Converte TNFe em NotaFiscalTO.
     */
    public static NotaFiscalTO createNotaFiscalTO(NFeDocument nfe) throws HSConverterException {
        if (nfe != null) {
            try {
                EmitenteTO emitenteTO = createEmitenteTO(nfe.getNFe().getInfNFe().getEmit());
                NotaFiscalTO notaFiscalTO = new NotaFiscalTO(emitenteTO);
                TNFe.InfNFe inf = nfe.getNFe().getInfNFe();
                TNFe.InfNFe.Ide ide = inf.getIde();
                
                notaFiscalTO.setVersao(inf.getVersao());
                notaFiscalTO.setCNF(ide.getCNF());
                notaFiscalTO.setNatOp(ide.getNatOp());
                notaFiscalTO.setIndPag(ide.getIndPag().toString());
                notaFiscalTO.setMod(ide.getMod().toString());
                notaFiscalTO.setSerie(ide.getSerie());
                notaFiscalTO.setNumero(ide.getNNF());
                notaFiscalTO.setDataEmissao(DateHelper.getDate(ide.getDEmi(), DateHelper.DATE_XML_FORMAT));
                notaFiscalTO.setDataSaidaEntrada(DateHelper.getDate(ide.getDSaiEnt(), DateHelper.DATE_XML_FORMAT));
                notaFiscalTO.setHoraSaidaEntrada(DateHelper.getDate(ide.getHSaiEnt(), DateHelper.TIME_FORMAT));
                notaFiscalTO.setTpNF(ide.getTpNF().toString());
                notaFiscalTO.setCMunFG(ide.getCMunFG());
                notaFiscalTO.setTpImp(ide.getTpImp().toString());
                notaFiscalTO.setTpEmis(ide.getTpEmis().toString());
                notaFiscalTO.setCDV(ide.getCDV());
                notaFiscalTO.setTpAmb(ide.getTpAmb().toString());
                notaFiscalTO.setFinNFe(ide.getFinNFe().toString());
                notaFiscalTO.setProcEmi(ide.getProcEmi().toString());
                notaFiscalTO.setVerProc(ide.getProcEmi().toString());
                notaFiscalTO.setClienteTO(DocumentConverter.createClienteTO(inf.getDest()));
                notaFiscalTO.setRetirada(DocumentConverter.createLocalTO(inf.getRetirada()));
                notaFiscalTO.setEntrega(DocumentConverter.createLocalTO(inf.getEntrega()));
                notaFiscalTO.setTransporteTO(DocumentConverter.createTransporteTO(inf.getTransp()));
                notaFiscalTO.setListaProdutos(DocumentConverter.creaListProdutoNotaTO(inf.getDetArray()));
                notaFiscalTO.setInfoTO(DocumentConverter.createInfoTO(inf.getInfAdic()));
                notaFiscalTO.setSituacao(nfe.getNFe().getSignature() == null ? SituacaoNFeEnum.EM_DIGITACAO : SituacaoNFeEnum.ASSINADA);
                notaFiscalTO.setXmlString(DocumentHelper.createDocumentString(nfe, true));
                
                return notaFiscalTO;
            } catch (Exception ex) {
                throw new HSConverterException(ex.getMessage(), ex);
            }
        }
        return null;
    }

    /**
     * Converte ProdutoNotaTO em TNFe.InfNFe.Det.
     */
    public static TNFe.InfNFe.Det createDet(ProdutoNotaTO produtoNotaTO) throws HSConverterException {
        if (produtoNotaTO != null) {
            TNFe.InfNFe.Det det = TNFe.InfNFe.Det.Factory.newInstance();
            
            det.setProd(createProd(produtoNotaTO.getProduto()));
            
            Imposto imposto = det.addNewImposto();
            if (produtoNotaTO.getICMS() != null) {
                imposto.setICMS(createICMS(produtoNotaTO.getICMS()));
                if (produtoNotaTO.getIPI() != null) {
                    imposto.setIPI(createIPI(produtoNotaTO.getIPI()));
                }
                if (produtoNotaTO.getII() != null) {
                    imposto.setII(createII(produtoNotaTO.getII()));
                }
            } else if (produtoNotaTO.getISSQN() != null) {
                imposto.setISSQN(createISSQN(produtoNotaTO.getISSQN()));
            }
            imposto.setPIS(createPIS(produtoNotaTO.getPIS()));
            if (produtoNotaTO.getPISST() != null) {
                imposto.setPISST(createPISST(produtoNotaTO.getPISST()));
            }
            imposto.setCOFINS(createCOFINS(produtoNotaTO.getCOFINS()));
            if (produtoNotaTO.getCOFINSST() != null) {
                imposto.setCOFINSST(createCOFINSST(produtoNotaTO.getCOFINSST()));
            }
            
            if (!StringHelper.isBlankOrNull(produtoNotaTO.getInformacaoesAdicionais())) {
                det.setInfAdProd(produtoNotaTO.getInformacaoesAdicionais());
            }
            return det;
        }
        return null;
    }

    /**
     * Converte TNFe.InfNFe.Det em ProdutoNotaTO.
     */
    public static ProdutoNotaTO createProdutoNotaTO(TNFe.InfNFe.Det det) throws HSConverterException {
        ProdutoNotaTO produtoNotaTO = null;
        if (det != null) {
            produtoNotaTO = new ProdutoNotaTO();
            produtoNotaTO.setProduto(createProdutoTO(det.getProd()));
            Imposto imposto = det.getImposto();
            if (imposto != null) {
                produtoNotaTO.setICMS(createIcmsTO(imposto.getICMS()));
                produtoNotaTO.setIPI(createIpiTO(imposto.getIPI()));
                produtoNotaTO.setII(createIITO(imposto.getII()));
                produtoNotaTO.setISSQN(createIssqnTO(imposto.getISSQN()));
                produtoNotaTO.setPIS(createPisTO(imposto.getPIS()));
                produtoNotaTO.setPISST(createPisSTTO(imposto.getPISST()));
                produtoNotaTO.setCOFINS(createCofinsTO(imposto.getCOFINS()));
                produtoNotaTO.setCOFINSST(createCofinsSTTO(imposto.getCOFINSST()));
            }
            produtoNotaTO.setInformacoesAdicionais(det.getInfAdProd());
        }
        return produtoNotaTO;
    }

    /**
     * Converte uma lista de ProdutoNotaTO em TNFe.InfNFe.Det[].
     */
    public static TNFe.InfNFe.Det[] createDetArray(List<ProdutoNotaTO> listaProdutos) throws HSConverterException {
        if (listaProdutos != null) {
            TNFe.InfNFe inf = TNFe.InfNFe.Factory.newInstance();
            
            for (int i = 0; i < listaProdutos.size(); i++) {
                TNFe.InfNFe.Det det = createDet(listaProdutos.get(i));
                if (det != null) {
                    det.setNItem(String.valueOf(i + 1));
                }
                inf.addNewDet();
                inf.setDetArray(i, det);
            }
            
            return inf.getDetArray();
        }
        return null;
    }

    /**
     * Converte TNFe.InfNFe.Det[] em uma lista de ProdutoNotaTO.
     */
    public static List<ProdutoNotaTO> creaListProdutoNotaTO(TNFe.InfNFe.Det[] detArray) throws HSConverterException {
        if (detArray != null) {
            List<ProdutoNotaTO> listProdutoNotaTO = new ArrayList<ProdutoNotaTO>();
            for (TNFe.InfNFe.Det det : detArray) {
                ProdutoNotaTO produtoNotaTO = createProdutoNotaTO(det);
                listProdutoNotaTO.add(produtoNotaTO);
            }
            return listProdutoNotaTO;
        }
        return null;
    }

    /**
     * Converte TransporteTO em TNFe.InfNFe.Transp.
     */
    public static TNFe.InfNFe.Transp createTransp(TransporteTO transporteTO) throws HSConverterException {
        return (Transp) createXmlObject(transporteTO, TNFe.InfNFe.Transp.class);
    }

    /**
     * Converte TNFe.InfNFe.Transp em TransporteTO.
     */
    public static TransporteTO createTransporteTO(TNFe.InfNFe.Transp transp) throws HSConverterException {
        return (TransporteTO) createBaseTO(transp, TransporteTO.class);
    }

    /**
     * Converte InfoTO em TNFe.InfNFe.InfAdic.
     */
    public static TNFe.InfNFe.InfAdic createInfAdic(InfoTO infoTO) throws HSConverterException {
        return (InfAdic) createXmlObject(infoTO, TNFe.InfNFe.InfAdic.class);
    }

    /**
     * Converte TNFe.InfNFe.InfAdic em InfoTO.
     */
    public static InfoTO createInfoTO(TNFe.InfNFe.InfAdic infAdic) throws HSConverterException {
        return (InfoTO) createBaseTO(infAdic, InfoTO.class);
    }

    /**
     * Converte LocalTO em TLocal.
     */
    public static TLocal createTLocal(LocalTO localTO) throws HSConverterException {
        if (localTO != null) {
            TLocal local = (TLocal) createXmlObject(localTO.getEnderecoTO(), TLocal.class);
            if (!StringHelper.isBlankOrNull(localTO.getCNPJ())) {
                local.setCNPJ(localTO.getCNPJ());
            }
            if (!StringHelper.isBlankOrNull(localTO.getCPF())) {
                local.setCPF(localTO.getCPF());
            }
            return local;
        }
        return null;
    }

    /**
     * Converte TLocal em LocalTO.
     */
    public static LocalTO createLocalTO(TLocal local) throws HSConverterException {
        if (local != null) {
            LocalTO localTO = (LocalTO) createBaseTO(local, LocalTO.class);
            localTO.setEnderecoTO((EnderecoTO) createBaseTO(local, EnderecoTO.class));
            return localTO;
        }
        return null;
    }
}
